#include "galileo_direct_texture_test.h"
#include "utils/fps.h"
#include "utils/task.h"

//#define  LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
//#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
//#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)

#define  LOGW(...)  printf(__VA_ARGS__)
#define  LOGE(...)  printf(__VA_ARGS__)
#define  LOGI(...)  printf(__VA_ARGS__)

const char *Shaders::vertexShaderSource = 
STRINGIFY (
    attribute vec4 position;
    attribute vec4 color;
    attribute vec2 texcoord;
    varying vec2 outexcoord;
    varying vec4 outcolor;  
    void main() {
        gl_Position = position;
        outcolor = color;
        outexcoord = texcoord;
    }
);

const char *Shaders::fragmentShaderSource =
STRINGIFY (
    precision mediump float;
    varying vec4 outcolor;
    uniform sampler2D sampler;
    varying vec2 outexcoord;
    void main() {
        vec4 imagePixel = texture2D(sampler, outexcoord);
        gl_FragColor = vec4(imagePixel.xyz, 1.0);
    }
);

int Shaders::compile(const char *shaderSource, GLuint ShaderNum) {
    glShaderSource(ShaderNum, 1, &shaderSource, 0);
    glCompileShader(ShaderNum);
    GLint compiled = 0;
    glGetShaderiv(ShaderNum, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
	    // Retrieve error buffer size.
	    GLint errorBufSize, errorLength;
	    glGetShaderiv(ShaderNum, GL_INFO_LOG_LENGTH, &errorBufSize);
	    char *infoLog = new char[errorBufSize * sizeof(char) + 1];
	    if (infoLog != 0) {
		    // Retrieve error.
		    glGetShaderInfoLog(ShaderNum, errorBufSize, &errorLength, infoLog);
		    infoLog[errorBufSize] = '\0';
		    LOGE("%s\n", infoLog);
            delete [] infoLog;
	    }
	    return 0;
    }
    return 1;
}

Shaders::Shaders() {
    vertShaderNum = 0;
    fragShaderNum = 0;
    programHandle = 0;
}
    
GLuint Shaders::load() {
    if (programHandle != 0) {
        return programHandle;
    }
    vertShaderNum = glCreateShader(GL_VERTEX_SHADER);
    fragShaderNum = glCreateShader(GL_FRAGMENT_SHADER);
    if (compile(vertexShaderSource, vertShaderNum) == 0) {
	    LOGE("%d: PS compile failed.\n", __LINE__);
	    return programHandle;
    }
    if (compile(fragmentShaderSource, fragShaderNum) == 0) {
	    LOGE("%d: VS compile failed.\n", __LINE__);
	    return programHandle;
    }
    programHandle = glCreateProgram();
    glAttachShader(programHandle, vertShaderNum);
    glAttachShader(programHandle, fragShaderNum);
    glLinkProgram(programHandle);
    // Check if linking succeeded.
    GLint linked = false;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linked);
    if (!linked) {
	    LOGE("%d: Link failed.\n", __LINE__);
	    // Retrieve error buffer size.
	    GLint errorBufSize, errorLength;
	    glGetShaderiv(programHandle, GL_INFO_LOG_LENGTH, &errorBufSize);
	    char *infoLog = new char[errorBufSize * sizeof(char) + 1];
	    if (infoLog != 0)  {
		    // Retrieve error.
		    glGetProgramInfoLog(programHandle, errorBufSize, &errorLength, infoLog);
		    infoLog[errorBufSize + 1] = '\0';
		    LOGE("%s", infoLog);
		    delete [] infoLog;
	    }
	    return programHandle;
    }
    glUseProgram(programHandle);
    LOGI("shader program loaded\n");
    return programHandle;
}

void Shaders::destroy() {
    if (programHandle) {
        glDeleteShader(vertShaderNum);
        glDeleteShader(fragShaderNum);
        glDeleteProgram(programHandle);
        glUseProgram(0);
        programHandle = 0;
    }
}

volatile sig_atomic_t Renderer::quit = 0;

const EGLint Renderer::gl_context_attribs[] = {
    EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL_NONE
};

const EGLint Renderer::s_configAttribs[] = {
    EGL_SAMPLES,         0,
    EGL_RED_SIZE,        8,
    EGL_GREEN_SIZE,      8,
    EGL_BLUE_SIZE,       8,
    EGL_ALPHA_SIZE,      8,
    EGL_DEPTH_SIZE,      0,
    EGL_SURFACE_TYPE,    EGL_WINDOW_BIT | EGL_LOCK_SURFACE_BIT_KHR,
    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    EGL_NONE
};

const GLfloat Renderer::position[][2] = {
    { -1.0f, -1.0f}, // C
    {  1.0f, -1.0f}, // D
    { -1.0f,  1.0f}, // A
    {  1.0f,  1.0f}, // B
};

const GLfloat Renderer::texcoords[][2] = {
    { 0.0f, 0.0f}, // C
    { 1.0f, 0.0f}, // D
    { 0.0f, 1.0f}, // A	    
    { 1.0f, 1.0f}, // B
};

const GLfloat Renderer::color[][3] = {
    {1.0f, 0.0f, 0.0f},
    {0.0f, 1.0f, 0.0f},
    {0.0f, 0.0f, 1.0f},
    {1.0f, 1.0f, 0.0f}
};
   
Renderer::Renderer() {
    eglWindowSurface = EGL_NO_SURFACE;
    eglDisplay = EGL_NO_DISPLAY;
    eglContext = EGL_NO_CONTEXT;
    programHandle = 0;
    init();
}
    
void Renderer::sighandler(int signal) {
    quit = 1;
}

void fillRGBA8Buffer(unsigned char *buf, int w, int h, int stride) {
    static const uint32_t colors[] = { 0xEF00FF00, 0xEFFF0000, 0xEF0000FF};
    static uint32_t* pColor = (uint32_t*)buf;
    static int i=0;

    for(int j=0;j<h; j++){
        for(int k=0;k<w; k++){
            pColor[w*j+k] = colors[i%3];
        }
    }
	i++;
}
    
void Renderer::draw(sp<GraphicBuffer> buffer) {
    void *p = 0;
    if(!p){
		buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &p);
		fillRGBA8Buffer(static_cast<unsigned char *>(p), 
		                displayWidth, 
		                displayHeight, 
		                displayWidth);
		buffer->unlock();   
	}else{
		fillRGBA8Buffer(static_cast<unsigned char *>(p), 
				            displayWidth, 
				            displayHeight, 
				            displayWidth);
	}
}
#define  CHECK_EGL_ERROR() do{ \
		int d=eglGetError();  \
		if (d != EGL_SUCCESS) \
		LOGE("EGL error[%s][%d]:0x%X\n",__FUNCTION__, __LINE__, d); \
}while(0)
EGLint lockAttrs[] =
    {
        EGL_LOCK_USAGE_HINT_KHR, EGL_WRITE_SURFACE_BIT_KHR,
        EGL_NONE
    };
void Renderer::draw() {
	eglLockSurfaceKHR(eglDisplay, eglWindowSurface, lockAttrs);
CHECK_EGL_ERROR();
    draw(textureBuffer);
 	eglUnlockSurfaceKHR(eglDisplay, eglWindowSurface);
CHECK_EGL_ERROR();
}

void Renderer::clear(sp<GraphicBuffer> buffer) {
    void *p = 0;
    buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &p);
    char *bitmap = static_cast<char *>(p);
    memset(bitmap, 255, displayWidth * displayHeight * 4);
    buffer->unlock();   
}

static void draw_fun(void* thiz){
	Renderer* render = (Renderer*)thiz;
	while(1){
		render->draw();
		usleep(1000000);
	}
}

void Renderer::render() {
	LOGI("%s - %d\n", __func__, __LINE__);

	pthread_t draw_thread = task_start(&draw_fun, (void*)this);	

    while (true) {
		fps_startFrame();
//		void *p = 0;
//        textureBuffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN, &p);
//        unsigned char *textureBitmap = static_cast<unsigned char *>(p);
//		glReadPixels(0, 0, displayWidth, displayHeight, GL_RGBA, GL_UNSIGNED_BYTE, textureBitmap);
		    glVertexAttribPointer(locPosition, 2, GL_FLOAT, GL_FALSE, 0, &position[0][0]);
    glVertexAttribPointer(locColor, 3, GL_FLOAT, GL_FALSE, 0, &color[0][0]);
    glVertexAttribPointer(locTexcoord, 2, GL_FLOAT, GL_FALSE, 0, &texcoords[0][0]);    
    glEnableVertexAttribArray(locPosition);
    glEnableVertexAttribArray(locColor);
    glEnableVertexAttribArray(locTexcoord);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(locSampler, 0);
//        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(0, 0, displayWidth, displayHeight);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);        
		glClear(GL_COLOR_BUFFER_BIT);    
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        EGLBoolean ret = eglSwapBuffers(eglDisplay, eglWindowSurface);
fps_endFrame();
        if (ret == EGL_FALSE) {
	        handle_egl_error("eglSwapBuffers");
	        break;
	    }
	    if (quit == 1) break;
    }

	LOGI("%s - %d\n", __func__, __LINE__);
}

void Renderer::init() {
	LOGI("%s - %d\n", __func__, __LINE__);
    surfaceComposerClient = new SurfaceComposerClient();
	sp<IBinder> dp = surfaceComposerClient->getBuiltInDisplay(0);
    DisplayInfo dinfo;
    status_t status = surfaceComposerClient->getDisplayInfo(dp, &dinfo);
	LOGI("%s - %d - %d\n", __func__, __LINE__, status);
    if (status) return;
    int w = dinfo.h;
	dinfo.h = dinfo.w;
    dinfo.w = w;
	LOGI("%s - %d - dinfo:%dx%d\n", __func__, __LINE__, dinfo.w, dinfo.h);
    surfaceControl = surfaceComposerClient->createSurface(String8("Renderer"), dinfo.w, dinfo.h, PIXEL_FORMAT_RGBA_8888, 0);
    SurfaceComposerClient::openGlobalTransaction();
    surfaceControl->setLayer(0x40000000);
    SurfaceComposerClient::closeGlobalTransaction();
    nativeSurface = surfaceControl.get()->getSurface();
    NativeWindowType eglWindow = nativeSurface.get(); 
    eglBindAPI(EGL_OPENGL_ES_API);
    eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    EGLint numConfigs;
    EGLint majorVersion;
    EGLint minorVersion;
    EGLConfig eglConfig;  
    eglInitialize(eglDisplay, &majorVersion, &minorVersion);
    eglGetConfigs(eglDisplay, 0, 0, &numConfigs);
    eglChooseConfig(eglDisplay, s_configAttribs, &eglConfig, 1, &numConfigs);
    eglWindowSurface = eglCreateWindowSurface(eglDisplay, eglConfig, eglWindow, 0);
    if (eglWindowSurface == EGL_NO_SURFACE) {
	    handle_egl_error("eglCreateWindowSurface");
	    quit = 1;
	    return;
    } else {
	   LOGI("%s:eglCreateWindowSurface ok\n", __func__);
    }     
    eglContext = eglCreateContext(eglDisplay, eglConfig, eglContext, gl_context_attribs);
    if (eglContext == EGL_NO_CONTEXT) {
	    handle_egl_error("eglCreateContext");
	    quit = 1;
	    return;
    } else {
	    LOGI("%s:eglCreateContext ok\n", __func__);
    }
    EGLBoolean ret = eglMakeCurrent(eglDisplay, eglWindowSurface, eglWindowSurface, eglContext);
    if (ret == EGL_FALSE) {
	    handle_egl_error("eglMakeCurrent");
	    quit = 1;
	    return;
    } else {
	    LOGI("%s:eglMakeCurrent ok\n", __func__);	  
    }
    if (eglQuerySurface(eglDisplay, eglWindowSurface, EGL_WIDTH, &displayWidth) == EGL_FALSE) {
        LOGE("%s, %d: Error, eglQuerySurface(EGL_WIDTH): 0x%x", __func__, __LINE__, eglGetError());
        quit = 1;
        return;
    }
    if (eglQuerySurface(eglDisplay, eglWindowSurface, EGL_HEIGHT, &displayHeight) == EGL_FALSE) {
        LOGE("%s, %d: Error, eglQuerySurface(EGL_HEIGHT): 0x%x", __func__, __LINE__, eglGetError());
        quit = 1;
        return;
    }
    LOGI("DISPLAY: %d*%d\n", displayWidth, displayHeight);
    programHandle = shaders.load();
    if(programHandle == 0) {
	    handle_egl_error("shaders.load() failed");
	    quit = 1;
	    return;
    } else {
	    LOGI("%s:shaders.load() ok\n", __func__);	  
    }
    locSampler = glGetUniformLocation(programHandle, "sampler");
	locPosition = glGetAttribLocation(programHandle, "position");
	locColor = glGetAttribLocation(programHandle, "color");
	locTexcoord = glGetAttribLocation(programHandle, "texcoord");
    sp<GraphicBuffer> buf1(new GraphicBuffer(displayWidth, 
                                             displayHeight, 
                                             PIXEL_FORMAT_RGBA_8888, 
                                             GraphicBuffer::USAGE_SW_READ_OFTEN | 
                                             GraphicBuffer::USAGE_SW_WRITE_OFTEN |
                                             GraphicBuffer::USAGE_HW_TEXTURE));
    textureBuffer = buf1;
    status_t err = textureBuffer->initCheck();
    if (err != NO_ERROR) {
	    LOGE("%s:textureBuffer->initCheck() failed!!! err=0x%x\n", __func__, err);
	    quit = 1;
	    return;
    }
    LOGI("%s:textureBuffer ok w=%d, h=%d, strid=%d\n", __func__, textureBuffer->getWidth(), 
                                                                 textureBuffer->getHeight(), 
                                                                 textureBuffer->getStride()); 
    clear(textureBuffer);              
    const EGLint imageAttrs[] = {
        EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
        EGL_NONE
    };

    image = eglCreateImageKHR(eglDisplay, 
                              EGL_NO_CONTEXT, 
                              EGL_NATIVE_BUFFER_ANDROID, 
                              static_cast<EGLClientBuffer>(textureBuffer->getNativeBuffer()), 
                              imageAttrs);
    if (image == EGL_NO_IMAGE_KHR) {
        LOGE("%s, %d: Error, createEGLImageKHR: 0x%x\n", __func__, __LINE__, eglGetError());
        quit = 1;
        return;
    }
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	LOGI("%s - %d\n", __func__, __LINE__);
}

void Renderer::deinit() {
    LOGI("Cleaning up...");
    glDeleteTextures(1, &texture);
    shaders.destroy();
    eglMakeCurrent(eglDisplay, 0, 0, 0);
    eglDestroyContext(eglDisplay, eglContext);
    eglDestroySurface(eglDisplay, eglWindowSurface);
    nativeSurface.clear();
    surfaceControl.clear();
    eglTerminate(eglDisplay);
    LOGI("Done\n");
}
    
void Renderer::handle_egl_error(const char *name) {
    EGLint error_code = eglGetError();
    LOGE("'%s' returned egl error (0x%x)\n", name, error_code);
}

int Renderer::run(int argc, char *argv[]) {
    if (argc > 1) {
        if (strcmp(argv[1],"-?") == 0) {
            usage();
        }
    }
    int parent = getpid();
    signal(SIGINT, Renderer::sighandler);

	Renderer *renderer = new Renderer();
	renderer->render();
	delete renderer;
    return EXIT_SUCCESS;
}

void Renderer::usage() {
    LOGI("Usage:\n"); 
    LOGI("galileo_direct_texture_test OPTIONS\n");   
    LOGI("OPTIONS are:\n");  
    LOGI("-? usage\n");    
}

Renderer::~Renderer() {
    deinit();
}

int main(int argc, char *argv[]) {
fps_init();
    exit(Renderer::run(argc, argv));
}
